#pragma once

#include "base/assert.hpp"
#include "fmt/core.h"

#include <cstddef>
#include <iostream>
#include <limits>
#include <memory>
#include <vector>

/*
 * 实现一个对象储物柜 Cabinet
 * 其特征在于:
 * 1) 存储一个对象指针后, 将获得一个 Token, 即称:"凭据":
 * 2) 该 Token 无法单位访问到对象, 需要通过 Cabinet.at(token) 才能获取到对象;
 * 3) 如果 Token 对应的对象被删除, 那么该 Token 就会失效. 失效了的 Token;
 * 取不出对象;  3) 使用 Token 获取对象、存入与取出对象的时间复杂度均为 O(1);
 *
 * 使用场景:
 * - TcpServer 服务器, 要管理其 TcpConnection 的生命期
 */

namespace ibox::cabinet {
using Id = size_t;
using Pos = size_t;

// 凭据
class Token {

public:
    // 思考: Token可以拷贝吗?
    // 可以
    // Token可以移动吗?
    // 可以, 但没必要, 简单的字段, 不涉及大量的拷贝
    Token() { std::cout << "Token();\n"; }
    Token(Id id_, Pos pos_) : id(id_), pos(pos_) {
        std::cout << "Token(id, pos);\n";
    }
    ~Token() { std::cout << "~Token();\n"; }
    inline void reset() {
        id = 0;
        pos = 0;
    }
    inline bool equal(const Token &other) const {
        return id == other.id && pos == other.pos;
    }
    inline bool less(const Token &other) const {
        return id != other.id ? id < other.id : pos < other.pos;
    }

    inline bool operator==(const Token &rhs) const { return equal(rhs); }
    inline bool operator!=(const Token &rhs) const { return !equal(rhs); }
    inline bool operator<(const Token &rhs) const { return less(rhs); }
    inline bool operator<=(const Token &rhs) const {
        return less(rhs) || equal(rhs);
    }
    inline bool operator>(const Token &rhs) const {
        return !less(rhs) && !equal(rhs);
    }
    inline bool operator>=(const Token &rhs) const { return !less(rhs); }

public:
    Id id{}; // id为0有特殊含义
    Pos pos{};
};

// 柜子
template <typename T>
class Cabinet {
public:
    // 预留储物空间
    void reserve(size_t size);
    // 存入对象, 返回Token, Token对象直接返回给调用方, 不在Cell中存
    Token save(T *obj);
    // 根据Token移除对象
    T *free(const Token *token);
    // 清空所有对象
    void clear();

    // 获取对象的指针
    T *at(const Token *token) const;
    T *operator[](const Token *token) { return at(token); }

    size_t size() const;
    bool empty() const { return size() == 0; }
    // 遍历
    template <typename Func>
    void foreach (Func func);

private:
    Id alloc_id();
    Pos alloc_pos();

private:
    // 单元格
    class Cell {
    public:
        // 为0时, 表示该格子为空闲状态
        // 非0时, 表示该格子存在对象
        Id id = 0;
        union {
            // 对象指针的地址
            T *obj_ptr = nullptr;
            // 下一个空闲格子的位置
            Pos next_free;
        };
        // std::unique_ptr<Token> token{};
    };

    Id m_last_id = 0;
    std::vector<Cell> m_cells;
    Pos m_first_free = std::numeric_limits<Pos>::max();
    size_t m_count;
};

template <typename T>
void Cabinet<T>::reserve(size_t size) {
    m_cells.reserve(size);
}

template <typename T>
Id Cabinet<T>::alloc_id() {
    // 避免分配 0 作为 id
    if (m_last_id == std::numeric_limits<Id>::max()) {
        m_last_id = 0;
    }

    return ++m_last_id;
}

template <typename T>
Pos Cabinet<T>::alloc_pos() {
    if (m_first_free != std::numeric_limits<Pos>::max()) {
        // 如果有空格子, 则直接使用已有空间
        Pos new_pos = m_first_free;
        Cell &cell = m_cells.at(new_pos);
        m_first_free = cell.next_free;
        return new_pos;
    }

    // 否则在尾部追加一个格子
    m_cells.emplace_back(Cell());
    return m_cells.size() - 1;
}

template <typename T>
Token Cabinet<T>::save(T *obj) {
    expect(obj != nullptr);
    // auto token = std::make_unique<Token>(alloc_id(), alloc_pos());
    Token token{alloc_id(), alloc_pos()};
    fmt::print("make token: id={},pos={}\n", token.id, token.pos);
    Cell &cell = m_cells.at(token.pos);
    // cell.token = std::move(token);
    cell.id = token.id;
    cell.obj_ptr = obj;
    return token;
}

template <typename T>
T *Cabinet<T>::free(const Token *token) {
    if (token->id == 0 || token->pos >= m_cells.size()) {
        return nullptr;
    }

    Cell &cell = m_cells.at(token->pos);
    if (cell.id == token->id) {
        T *ptr = cell.obj_ptr;
        cell.id = 0;
        cell.next_free = m_first_free;
        m_first_free = token->pos;
        m_count--;
        return ptr;
    }
    return nullptr;
}

template <typename T>
void Cabinet<T>::clear() {
    m_last_id = 0;
    m_cells.clear();
    m_first_free = std::numeric_limits<Pos>::max();
    m_count = 0;
}

template <typename T>
T *Cabinet<T>::at(const Token *token) const {
    if (token->id == 0 || token->pos >= m_cells.size()) {
        fmt::print("unexpected token: id={},pos={}\n", token->id, token->pos);
        return nullptr;
    }

    const Cell &cell = m_cells.at(token->pos);
    if (cell.id == token->id) {
        return cell.obj_ptr;
    }
    fmt::print("token not found: id={},pos={}\n", token->id, token->pos);
    return nullptr;
}

template <typename T>
size_t Cabinet<T>::size() const {
    return m_count;
}

template <typename T>
template <typename Func>
void Cabinet<T>::foreach (Func func) {
    for (auto &cell : m_cells) {
        if (cell.id != 0) {
            func(cell.obj_ptr);
        }
    }
}

} // namespace ibox::cabinet
